package com.wyw.hemerocallis.service.impl;

import com.alibaba.fastjson.JSON;
import com.wyw.hemerocallis.common.TableId;
import com.wyw.hemerocallis.constant.GoodsConstant;
import com.wyw.hemerocallis.entity.HemerocallisGoods;
import com.wyw.hemerocallis.goods.DeductGoodsInventory;
import com.wyw.hemerocallis.goods.GoodsInfo;
import com.wyw.hemerocallis.goods.SimpleGoodsInfo;
import com.wyw.hemerocallis.repository.HemerocallisGoodsRepository;
import com.wyw.hemerocallis.service.IGoodsService;
import com.wyw.hemerocallis.vo.PageSimpleGoodsInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.collections4.map.HashedMap;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.print.DocFlavor;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 商品微服务相关功能实现
 *
 * @author Mr Wu    yewen.wu.china@gmail.com
 * <p>
 * Update History:
 * Author        Time            Content
 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class GoodsServiceImpl implements IGoodsService {

    private final StringRedisTemplate redisTemplate;
    private final HemerocallisGoodsRepository hemerocallisGoodsRepository;

    /**
     * 通过tableId 在redis缓存获得简单的商品信息
     * @param tableId   主键ids
     * @return          简单的商品信息集合
     */
    @Override
    public List<GoodsInfo> getGoodsInfoByTableId(TableId tableId) {
        //详细的商品信息，不能从redis cache中拿
        List<Long> ids = tableId.getIds().stream()
                .map(TableId.Id::getId)
                .collect(Collectors.toList());
        log.info("get goods info by ids:{}", ids);

        List<HemerocallisGoods> goods = IterableUtils.toList(
                hemerocallisGoodsRepository.findAllById(ids)
        );

        return goods.stream()
                .map(HemerocallisGoods::toGoodsInfo)
                .collect(Collectors.toList());
    }

    /**
     * 分页获取简单的商品信息
     * @param page  分页页码
     * @return      分页的商品信息
     */
    @Override
    public PageSimpleGoodsInfo getSimpleGoodsInfoByPage(int page) {
        //分页信息不能从reids cache中拿
        if(page <= 1) {
            page = 1;//默认第一页
        }
        //这里分页规则可以改，比如每页显示也可以传进来
        Pageable pageable = PageRequest.of(
                page -1, 10, Sort.by("id").descending()
        );
        Page<HemerocallisGoods> orderPage =
                hemerocallisGoodsRepository.findAll(pageable);

        //是否还有更多页
        boolean hasMore = orderPage.getTotalPages() > page;

        return new PageSimpleGoodsInfo(
                orderPage.getContent().stream()
                .map(HemerocallisGoods::toSimple)
                .collect(Collectors.toList()), hasMore
        );
    }

    /**
     * 通过ID查询简单的商品信息
     * @param tableId   主键ID
     * @return          简单商品信息的集合
     */
    @Override
    public List<SimpleGoodsInfo> getSimpleGoodsInfoByTableId(TableId tableId) {
        //获取商品的简单信息，可以从redis cache获取，拿不到就从DB中获取并保存到redis中
        //redis的KV都是字符串类型
        List<Object> goodIds = tableId.getIds().stream()
                .map(id -> id.getId().toString()).collect(Collectors.toList());

        // FIXME 如果 cache 中查不到 goodsId 对应的数据, 返回的是 null, [null, null]
        List<Object> cachedSimpleGoodsInfos = redisTemplate.opsForHash()
                .multiGet(GoodsConstant.HEMEROCALLIS_GOODS_DICT_KEY, goodIds)
                .stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        //如果从redis中查到了商品信息，分两种情况操作
        if(CollectionUtils.isNotEmpty(cachedSimpleGoodsInfos)) {
            //1、从redis中查询的所有需要的SimpleGoodsInfo
            if(cachedSimpleGoodsInfos.size() == goodIds.size()) {
                log.info("get simple goods info by ids (from cache): {}",
                        JSON.toJSONString(goodIds));
                return parseCachedGoodsInfo(cachedSimpleGoodsInfos);
            } else {
                //2、一部分来自数据表（right），一部分来自redis cache（left）
                List<SimpleGoodsInfo> left = parseCachedGoodsInfo(cachedSimpleGoodsInfos);
                //取差集：传递进来的参数 - 缓存中查到的 = 缓存中没有的
                Collection<Long> subtractIds = CollectionUtils.subtract(
                        goodIds.stream()
                        .map(g -> Long.valueOf(g.toString())).collect(Collectors.toList()),
                        left.stream()
                        .map(SimpleGoodsInfo::getId).collect(Collectors.toList())
                );
                // 缓存中没有的, 查询数据表并缓存
                List<SimpleGoodsInfo> right = queryGoodsFromDBAndCacheToRedis(
                        new TableId(subtractIds.stream().map(TableId.Id::new)
                                .collect(Collectors.toList()))
                );
                // 合并 left 和 right 并返回
                log.info("get simple goods info by ids (from db and cache): {}",
                        JSON.toJSONString(subtractIds));
                return new ArrayList<>(CollectionUtils.union(left, right));
            }
        } else {
            // 从 redis 里面什么都没有查到
            List<SimpleGoodsInfo> simpleGoodsInfos = queryGoodsFromDBAndCacheToRedis(tableId);
            log.info("get simple goods info by ids (from db and cache): {}",
                    JSON.toJSONString(simpleGoodsInfos));
            return simpleGoodsInfos;
        }
    }

    @Override
    public Boolean deductGoodsInventory(List<DeductGoodsInventory> deductGoodsInventories) {
        //校验参数是否合法
        deductGoodsInventories.forEach(d -> {
            if(d.getCount() <= 0) {
                throw new RuntimeException("purchase goods count need > 0");
            }
        });

        List<HemerocallisGoods> hemerocallisGoods = IterableUtils.toList(
                hemerocallisGoodsRepository.findAllById(
                        deductGoodsInventories.stream()
                        .map(DeductGoodsInventory::getGoodsId)
                        .collect(Collectors.toList())
                )
        );

        //根据传递的goodIds查询不到商品对象，抛出异常
        if(CollectionUtils.isEmpty(hemerocallisGoods)) {
            throw new RuntimeException("can not found any goods by request");
        }

        //查询出来的商品数量与传递的不一致，抛出异常
        if(hemerocallisGoods.size() != deductGoodsInventories.size()) {
            throw new RuntimeException("request is not valid");
        }

        //goodsId -> DeductGoodsInventory
        Map<Long, DeductGoodsInventory> goodsId2Inventory =
                deductGoodsInventories.stream().collect(
                        Collectors.toMap(DeductGoodsInventory::getGoodsId, Function.identity())
                );

        //检查是不是可以扣减库存，再去扣减库存
        hemerocallisGoods.forEach(g -> {
            Long currentInventory = g.getInventory();
            Integer needDeductInventory = goodsId2Inventory.get(g.getId()).getCount();
            if(currentInventory < needDeductInventory) {
                log.error("goods inventory is not enough: {}, {}",
                        currentInventory, needDeductInventory);
                throw new RuntimeException("goods inventory is not enough: " + g.getId());
            }
            //扣减库存
            g.setInventory(currentInventory - needDeductInventory);
            log.info("deduct goods inventory: {}, {}, {}", g.getId(),
                    currentInventory, g.getInventory());
        });

        hemerocallisGoodsRepository.saveAll(hemerocallisGoods);
        log.info("deduct goods inventory done");

        return true;
    }

    /**
     * 将缓存中的数据反序列化成Java Pojo对象
     * @param cachedSimpleGoodsInfo
     * @return
     */
    private List<SimpleGoodsInfo> parseCachedGoodsInfo(List<Object> cachedSimpleGoodsInfo) {
        return cachedSimpleGoodsInfo.stream()
                .map(s -> JSON.parseObject(s.toString(), SimpleGoodsInfo.class))
                .collect(Collectors.toList());
    }

    /**
     * 数据表中查询数据，并缓存到Redis中
     * @param tableId   tableId
     * @return          简单商品集合
     */
    private List<SimpleGoodsInfo> queryGoodsFromDBAndCacheToRedis(TableId tableId) {
        //从数据表中查询数据并做转换
        List<Long> ids = tableId.getIds().stream()
                .map(TableId.Id::getId)
                .collect(Collectors.toList());
        log.info("get simple goods info by ids (from db): {}",
                JSON.toJSONString(ids));

        List<HemerocallisGoods> hemerocallisGoods = IterableUtils.toList(
                hemerocallisGoodsRepository.findAllById(ids)
        );

        List<SimpleGoodsInfo> result = hemerocallisGoods.stream()
                .map(HemerocallisGoods::toSimple)
                .collect(Collectors.toList());
        // 将结果缓存, 下一次可以直接从 redis cache 中查询
        log.info("cache goods info: {}", JSON.toJSONString(ids));

        Map<String, String> id2JsonObject = new HashedMap<>(result.size());
        result.forEach(g -> id2JsonObject.put(
                    g.getId().toString(), JSON.toJSONString(g)
            ));

        //保存至redis
        redisTemplate.opsForHash().putAll(
                GoodsConstant.HEMEROCALLIS_GOODS_DICT_KEY, id2JsonObject);
        return result;

    }
}
